package EstimatorPackage;

import EstimatorPackage.Expressions.ComplexExpression;
import EstimatorPackage.Expressions.EmptyExpression;
import EstimatorPackage.Expressions.Expression;
import EstimatorPackage.Expressions.ListExpression;
import EstimatorPackage.Expressions.SimpleExpression;
import HierarchyPackage.HClass;
import HierarchyPackage.Hierarchy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

/**
 *
 * @author Martin Haulrich
 */
public abstract class AbstractEstimatorExp implements EstimatorExp {

    static final int DIFFERENCE = 1;

    static final int INTERSECTION = 3;

    static final int UNION = 2;

    final Expression empty;

    public AbstractEstimatorExp() {
        empty = new EmptyExpression();
    }

    @Override
    public double getEstimate(Expression pe) {
        //    System.err.println("hej");
        // We convert the ecpression to a probability-expression and use ProbAlgebra to try and reduce it
        if (true) {
            throw new RuntimeException("should not be called");
        }
        return 0;
    }

    @Override
    public double getEstimate(Expression e, Hierarchy hier) {
//        Double est = cache.get(e);
//        if (est == null) {
//            est = getEstimateIntenal(e, hier);
//            cache.put(e, est);
//        }
//        return est;
        return getEstimateInternal(e, hier);
    }

    private double getEstimateInternal(Expression e, Hierarchy hier) {

        if (e.type() == Expression.EMPTY) {
            return Consts.logZero;
        }

        if (e.type() == Expression.COMPLEX) {

            ComplexExpression ce = (ComplexExpression) e;
            if (ce.operation == ComplexExpression.DIFFERENCE) {

                Expression e2 = ce.e2;

//                if (ce.e1.type() == Expression.SIMPLE && e2.type() == Expression.LIST) {
//                    e2 = ocExpReduce((SimpleExpression) ce.e1, (ListExpression) e2, hier);
//                }

                double se1 = getEstimate(ce.e1, hier);

                Expression inter = new ComplexExpression(ComplexExpression.INTERSECTION, ce.e1, e2);
                double se2 = getEstimate(inter, hier);
                double l = Math.max(se1, se2);
                double res = l + Math.log10(Math.pow(10, se1 - l) - Math.pow(10, se2 - l));
                return res;
            }

            if (ce.operation == ComplexExpression.UNION) {

                Expression intersection = new ComplexExpression(ComplexExpression.INTERSECTION, ce.e1, ce.e2);
                double se1 = getEstimate(ce.e1, hier);
                double se2 = getEstimate(ce.e2, hier);
                double is = getEstimate(intersection, hier);

                double l = Math.max(se1, se2);
                double tmp = l + Math.log10(Math.pow(10, se1 - l) + Math.pow(10, se2 - l));

                l = Math.max(tmp, is);
                double res = l + Math.log10(Math.pow(10, tmp - l) - Math.pow(10, is - l));

                return res;

            }
            if (ce.operation == ComplexExpression.INTERSECTION) {

//                return getEstimate(ClassAlgebra.ClassAlgebraSingleton2.getInstance().mapIntersection(ce, hier), hier);
                return getEstimate(mapIntersection(ce, hier), hier);
//                return getEstimate(ClassAlgebra.ClassAlgebraSingleton.getInstance().map(e, hier), hier);
            }
        }
        if (e.type() == Expression.SIMPLE) {

            double es = getEstimate(((SimpleExpression) e).c, hier);
            return es;
        }
        if (e.type() == Expression.LIST) {
            ListExpression le = (ListExpression) e;
            if (le.operation != ListExpression.UNION) {
                throw new UnsupportedOperationException("Do we really have intersect lists?");
            }
            return getEstimateList(le, hier);
//            return getEstimateList(reduceList(le,hier), hier);
        }

        System.err.println("Nothing - impossible: " + e.type());
        return 0;

    }

    public static ListExpression ocExpReduce(SimpleExpression mainExp, ListExpression subExp, Hierarchy hier) {

//        Collection<Expression> subs = new ArrayList<Expression>(subExp.expList);
        Collection<Expression> subs = subExp.expList;

//        System.err.println("------------------------------------------");


        for (Iterator<Expression> it = subs.iterator(); it.hasNext();) {
            Expression s = it.next();
            // It probably always is a simpleexp
            if (s instanceof SimpleExpression) {
                SimpleExpression ss = (SimpleExpression) s;
                if (hier.getIntersection(mainExp.c, ss.c).isEmpty()) {
                    it.remove();
                }
            }
        }
//        System.err.println("+++++: " + a + " -> " + subs.size());

        ListExpression newSubsExp = new ListExpression(ListExpression.UNION, subs);

        return newSubsExp;
    }

    public static ListExpression reduceList(ListExpression le, Hierarchy hier) {

        Set<Expression> deleted = new HashSet<Expression>();

        List<Expression> list = new ArrayList<Expression>(le.expList);
        int i = 0;
        while (i < list.size() - 1) {
            Expression leftElement = list.get(i);
            boolean done = false;
            if (leftElement.type() == Expression.SIMPLE) {
                SimpleExpression leftSimple = (SimpleExpression) leftElement;
                HClass leftClass = leftSimple.c;
                int j = i + 1;
                while (j < list.size() && !done) {
                    Expression rightElement = list.get(j);

                    if (rightElement.equals(leftElement)) {
                        list.remove(j);
                    } else {

                        if (rightElement.type() == Expression.SIMPLE) {
                            SimpleExpression rightSimple = (SimpleExpression) rightElement;
                            HClass rightClass = rightSimple.c;

                            if (hier.superclassOf(leftClass, rightClass)) {
                                list.remove(j);
                            }
                            if (hier.superclassOf(rightClass, leftClass)) {
                                deleted.add(leftElement);
                                done = true;
                            }
                        }

                    }
                    j++;
                }
            }
            i++;
        }
        list.removeAll(deleted);
        return new ListExpression(ListExpression.UNION, list);

    //return le;
    }

    private double getEstimateList(ListExpression le, Hierarchy hier) {

        if (le.expList.isEmpty()) {
            return Consts.logZero;
        }

        List<Expression> list = new ArrayList<Expression>(le.expList);
        if (list.size() == 1) {
            return getEstimate(list.get(0), hier);
        }

        Expression firstElement = list.remove(0);
        ListExpression newListExp = new ListExpression(ListExpression.UNION, list);

        ComplexExpression inter = new ComplexExpression(ComplexExpression.INTERSECTION, firstElement, newListExp);

        double se1 = getEstimate(firstElement, hier);
        if (se1 < Consts.logZero) {
            return Consts.logZero;
        }
        double se2 = getEstimateList(newListExp, hier);
        double is = getEstimate(inter, hier);

        double l = Math.max(se1, se2);
        double tmp = l + Math.log10(Math.pow(10, se1 - l) + Math.pow(10, se2 - l));

        l = Math.max(tmp, is);
        double res = l + Math.log10(Math.pow(10, tmp - l) - Math.pow(10, is - l));

        return res;
    }

    public Expression mapIntersection(ComplexExpression e, Hierarchy hier) {
//        Expression ee = cache.get(e);
//        if (ee == null) {
//            ee = mapIntersectionInternal(e, hier);
//            cache.put(e, ee);
//        }
        Expression ee = mapIntersectionInternal(e, hier);
        return ee;
    }

    /**
     * Mapping of a complex expression with the intersection-operator
     * @param e
     * @param hier
     * @return
     *
     */
    private Expression mapIntersectionInternal(ComplexExpression e, Hierarchy hier) {

        if (e.e1.equals(e.e2)) {
            return e.e1;
        }

        if (e.e1.type() == Expression.EMPTY || e.e2.type() == Expression.EMPTY) {
            return empty;
        }

        // If both e1 and e2 are simple and there is a hierarchy available this can be used for further reductions
        if ((e.e1.type() == Expression.SIMPLE) && (e.e2.type() == Expression.SIMPLE)) {

            if (hier != null) {
                // If e2 is a superclass to e1 then return e1, if e1 is a superclass to e2 then return e2

                if (hier.superclassOf(((SimpleExpression) e.e2).c, ((SimpleExpression) e.e1).c)) {
                    return e.e1;
                }

                if (hier.superclassOf(((SimpleExpression) e.e1).c, ((SimpleExpression) e.e2).c)) {
                    return e.e2;
                }

                // Get the intersection of the two classes using the hierarchy
                Set<HClass> intersect = hier.getIntersection(((SimpleExpression) e.e1).c, ((SimpleExpression) e.e2).c);

                if (intersect.isEmpty()) {
                    return empty;
                }

                if (intersect.size() == 1) {
                    return new SimpleExpression(intersect.iterator().next());

                }

            // If the intersection of the two classes are the classes themselves, i.e. the size of the
            // intersection is 2 there os no reduction to me made
            // This should not be possible

            }
        }

        ComplexExpression a = null;

        Expression b = null;

        boolean todoComplex = false;

        boolean todoList = false;

        // At least one of the expression has to be complex for there to be anything to do
        // Make 'a' that expression.
        if (e.e1.type() == Expression.COMPLEX) {
            a = (ComplexExpression) e.e1;
            b = e.e2;
            todoComplex = true;
        } else {
            if (e.e2.type() == Expression.COMPLEX) {
                a = (ComplexExpression) e.e2;
                b = e.e1;
                todoComplex = true;
            }
        }

        if (todoComplex) {

            // a ∩ (b - c) = (a ∩ b) - (a ∩ c)
            if (a.operation == DIFFERENCE) {

                Expression newE1 = mapIntersection(new ComplexExpression(INTERSECTION, a.e1, b), hier);
                Expression newE2 = mapIntersection(new ComplexExpression(INTERSECTION, a.e2, b), hier);
                return new ComplexExpression(DIFFERENCE, newE1, newE2);
            }

            // a ∩ (b ∪ c) = (a ∩ b) ∪ (a ∩ c)
            if (a.operation == UNION) {
                Expression newE1 = mapIntersection(new ComplexExpression(INTERSECTION, a.e1, b), hier);
                Expression newE2 = mapIntersection(new ComplexExpression(INTERSECTION, a.e2, b), hier);
                return new ComplexExpression(UNION, newE1, newE2);
            }
        }

        // If one of the expression is a list expression make c that expression
        ListExpression c = null;

        if (e.e1.type() == Expression.LIST) {
            c = (ListExpression) e.e1;
            b = e.e2;
            todoList = true;
        } else {
            if (e.e2.type() == Expression.LIST) {
                c = (ListExpression) e.e2;
                b = e.e1;
                todoList = true;
            }
        }

        if (todoList) {

            // a ∩ ⋃b = ⋃a∩b
            if (c.operation == UNION) {
                Set<Expression> newList = new HashSet<Expression>();
                for (Expression cl : c.expList) {
                    Expression c1m = mapIntersection(new ComplexExpression(INTERSECTION, b, cl), hier);
                    newList.add(c1m);
                }

                return new ListExpression(UNION, newList);
            }
        }

        Expression ne1 = e.e1;
        if (e.e1.type() == Expression.COMPLEX) {
            ComplexExpression ce1 = (ComplexExpression) e.e1;
            if (ce1.operation == ComplexExpression.INTERSECTION) {
                ne1 = mapIntersection(ce1, hier);
            }
        }

        Expression ne2 = e.e2;
        if (e.e2.type() == Expression.COMPLEX) {
            ComplexExpression ce2 = (ComplexExpression) e.e2;
            if (ce2.operation == ComplexExpression.INTERSECTION) {
                ne2 = mapIntersection(ce2, hier);
            }
        }

        return mapIntersection(new ComplexExpression(INTERSECTION, ne1, ne2), hier);
    }

    public abstract double getEstimate(HClass h, Hierarchy hier);
}
